﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Collections.Generic;
using System.Threading;
using MusicStore.Silverlight.Infrastructure;

namespace Structura.Silverlight.Infrastructure.ViewModel
{
	/// <summary>
	/// base class for Domain Model, DTO and ViewModel classes
	/// </summary>
	public class Model : INotifyPropertyChanged, IDisposable
	{
		/// <summary>
		/// a dictionary to cache property changed event args generated for each property
		/// </summary>
		private static readonly Dictionary<string, PropertyChangedEventArgs> _eventArgsMap =
			 new Dictionary<string, PropertyChangedEventArgs>();

		/// <summary>
		/// function to assign the value of a property to an internal member
		/// and to raid the corresponding property chanhed event
		/// </summary>
		/// <typeparam name="T">Type of the property to set</typeparam>
		/// <param name="field">internal variable that holds the value</param>
		/// <param name="value">new value to set</param>
		/// <param name="propName">name of the property</param>
		protected void InnerSet<T>(ref T field, T value, string propName)
		{
			if ((field == null) && (value == null)) return;
			if ((field != null) && (field.Equals(value))) return;
			field = value;
			RaisePropertyChanged(propName);
		}

		protected void InnerSet<T>(ref T field, T value, params string[] propsName)
		{
			if ((field == null) && (value == null)) return;
			if ((field != null) && (field.Equals(value))) return;
			field = value;
			RaisePropertyChanged(propsName);
		}

		/// <summary>
		/// Get a PropertyChangedEventArgs instance fromt he dictionary or
		/// create a new one if not present
		/// </summary>
		/// <param name="propertyName">name of the property</param>
		/// <returns>Instance of the class</returns>
		private static PropertyChangedEventArgs GetEventArgs(string propertyName)
		{
			PropertyChangedEventArgs pe;
			if (_eventArgsMap.TryGetValue(propertyName, out pe) == false)
			{
				pe = new PropertyChangedEventArgs(propertyName);
				_eventArgsMap[propertyName] = pe;
			}
			return pe;
		}

		/// <summary>
		/// Raises a change notification event to signal a change in the
		/// specified property's value.
		/// </summary>
		/// <param name="propertyName">The property that has changed.</param>
		protected void RaisePropertyChanged(string propertyName)
		{
			if (String.IsNullOrEmpty(propertyName))
				throw new ArgumentNullException("propertyName");
			if (PropertyChanged == null) return;
			if (SynchronizationContext.Current != null)
				SynchronizationContext.Current.Post(delegate { OnPropertyChanged(propertyName); }, null);
			else
				UiDispatcher.Run(() => OnPropertyChanged(propertyName));
		}

		/// <summary>
		/// Raises a change notification event to signal a change in the
		/// specified properties.
		/// </summary>
		/// <param name="propertyNames">The properties that have changed.</param>
		protected void RaisePropertyChanged(params string[] propertyNames)
		{
			if ((propertyNames == null) || (propertyNames.Length == 0))
				throw new ArgumentNullException("propertyNames");

			if (SynchronizationContext.Current != null)
				SynchronizationContext.Current.Post(state =>
				{
					OnPropertiesChanged(propertyNames);
				}, null);
			else
				UiDispatcher.Run(() =>
				{
					OnPropertiesChanged(propertyNames);
				});
		}

		#region Implementation of INotifyPropertyChanged

		/// <summary>
		/// lets make it virtual to be used with ORMs proxies
		/// </summary>
		public virtual event PropertyChangedEventHandler PropertyChanged;

		protected void OnPropertyChanged(string propertyName)
		{
			if (PropertyChanged != null)
				PropertyChanged(this, GetEventArgs(propertyName));
		}

		private void OnPropertiesChanged(string[] propertyNames)
		{
			foreach (string propertyName in propertyNames)
				OnPropertyChanged(propertyName);
		}

		#endregion

		#region IDisposable Members

		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		protected virtual void Dispose(bool disposing)
		{
			if (mDisposed) return;
			mDisposed = true;
			if (disposing)
			{
				// TODO: free other state (managed objects).
			}
			// TODO: free your own state (unmanaged objects).
			// TODO: set large fields to null.
		}
		private Boolean mDisposed = false;

		#endregion

	}
}
